{
 "cells": [
  {
   "attachments": {},
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# 函数调用(Function Calling)\n",
    "\n",
    "文心一言提供函数调用功能，模型根据用户需求以及对函数的描述确定何时以及如何调用函数。函数调用功能的典型使用流程如下：\n",
    "\n",
    "(1) 用户提供对一组函数的名称、功能、请求参数（输入参数）和响应参数（返回值）的描述； \n",
    "\n",
    "(2) 模型根据用户需求以及函数描述信息，智能确定是否应该调用函数、调用哪一个函数、以及在调用该函数时需要如何设置输入参数； \n",
    "\n",
    "(3) 用户根据模型的提示调用函数，并将函数的响应传递给模型； \n",
    "\n",
    "(4) 模型综合对话上下文信息，以自然语言形式给出满足用户需求的回答。\n",
    "\n",
    "借由函数调用，用户可以从大模型获取结构化数据，进而利用编程手段将大模型与已有的内外部API结合以构建应用。\n",
    "\n",
    "在ERNIE Bot中，erniebot.ChatCompletion.create接口提供函数调用功能。关于该接口的更多详情请参考[ChatCompletion API文档](../../docs/api_reference/chat_completion.md)。"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "<div align=\"left\">\n",
    "\n",
    "| 字段名称    | 数据类型 | 字段描述                         |\n",
    "| :-------------- | ---------- | ---------------------------------- |\n",
    "| name        | str      | API（函数）名称                  |\n",
    "| description | str      | API(函数）功能描述               |\n",
    "| parameters  | dict     | API(函数）输入参数（以dict输入） |\n",
    "| responses   | dict     | API(函数）输出结果（以dict返回） |\n",
    "\n",
    "</div>\n",
    "\n",
    "parameter 和 responses 的相关字段情况如下\n",
    "\n",
    "\n",
    "| 字段名     | 数据类型  | 描述                                                |\n",
    "| :------------- | ----------- | ----------------------------------------------------- |\n",
    "| type       | str       | 此层级一般为object                                  |\n",
    "| properties | dict      | 变量的详细情况，键为变量名，值为变量的相关信息（dict），该字典详见下表                 |\n",
    "| required   | List[str] | 函数的输入参数中必须含有的变量，responses不含此字段 |\n",
    "\n",
    "\n",
    "对于properties中的变量的字典，相关的键值对如下\n",
    "\n",
    "\n",
    "| properties中的变量字典的元素 | 是否必须     | 描述                 |\n",
    "| :----------------------------- | ---------- | ---------------------- |\n",
    "| type                         | Required | 此层级一般为string，也可以使用integral等，但是因为整体为文本返回，差别不是特别大，使用时请注意函数内部转换     |\n",
    "| description                  | Optional   | 该变量的描述         |\n",
    "| enum                         | Optional   | 该变量的可选参数 |\n",
    "| default                         | Optional   | 该变量的默认参数 |\n",
    "\n",
    "\n"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "以下是一个简单的使用示例"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Step 1 \n",
    "用户提供对一组函数的名称、功能、请求参数（输入参数）和响应参数（返回值）的描述（可使用以下类进行统一管理）。 "
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 1,
   "metadata": {},
   "outputs": [],
   "source": [
    "functions = [\n",
    "    {\n",
    "        \"name\": \"add_function\",\n",
    "        \"description\": \"计算两个数的和\",\n",
    "        \"parameters\": {\n",
    "            \"type\": \"object\",\n",
    "            \"properties\": {\n",
    "                \"a\": {\"type\": \"string\", \"description\": \"第一个数\"},\n",
    "                \"b\": {\"type\": \"string\", \"description\": \"第二个数\"},\n",
    "            },\n",
    "            \"required\": [\"a\", \"b\"],\n",
    "        },\n",
    "        \"responses\": {\"type\": \"object\", \"properties\": {}},\n",
    "    },\n",
    "    {\n",
    "        \"name\": \"minus_function\",\n",
    "        \"description\": \"计算两个数的差\",\n",
    "        \"parameters\": {\n",
    "            \"type\": \"object\",\n",
    "            \"properties\": {\n",
    "                \"a\": {\"type\": \"string\", \"description\": \"第一个数\"},\n",
    "                \"b\": {\"type\": \"string\", \"default\": \"1.0\"},\n",
    "            },\n",
    "            \"required\": [\"a\"],\n",
    "        },\n",
    "        \"responses\": {\"type\": \"object\", \"properties\": {}},\n",
    "    },\n",
    "]"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Step 2\n",
    "模型根据用户需求以及函数描述信息，智能确定是否应该调用函数、调用哪一个函数、以及在调用该函数时需要如何设置输入参数； \n"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "在有了添加完function之后，即可调用function calling功能让大模型进行思考以及决策\n",
    "function call功能中返回的response的相关字段为\n",
    "\n",
    "| name     | thoughts     | arguments  |\n",
    "| :----------- | -------------- | ------------ |\n",
    "| 函数名称 | 模型思考过程 | 函数的输入 |\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 14,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "{'name': 'add_function', 'thoughts': '用户的问题是关于1加2的计算，我可以使用add_function工具来计算这两个数的和。', 'arguments': '{\"a\":\"1\",\"b\":\"2\"}'}\n"
     ]
    }
   ],
   "source": [
    "import erniebot\n",
    "from typing import List\n",
    "\n",
    "erniebot.api_type = \"aistudio\"\n",
    "erniebot.access_token = \"<eb-access-token>\"\n",
    "\n",
    "response = erniebot.ChatCompletion.create(\n",
    "    model=\"ernie-4.0\",\n",
    "    messages=[\n",
    "        {\n",
    "            \"role\": \"user\",\n",
    "            \"content\": \"1加2等于多少\",\n",
    "        },\n",
    "    ],\n",
    "    functions=functions,\n",
    ")\n",
    "function_call = response.function_call\n",
    "print(function_call)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Step 3\n",
    "用户根据模型的提示调用函数，并将函数的响应传递给模型； "
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 15,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "3.0"
      ]
     },
     "execution_count": 15,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "function_return = eval(response.get_result()[\"name\"])(**eval(response.get_result()[\"arguments\"]))\n",
    "function_return"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# Step 4[Option]\n",
    "模型综合对话上下文信息，以自然语言形式给出满足用户需求的回答。\n",
    "\n",
    "这一步不是必须的，用户可以不经过此步骤直接输出答案，这一步主要针对有需要再使用大模型进行进一步提炼、总结插件返回结果的场景，比如搜索插件、代码执行插件、计算器插件等别的插件。"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 16,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "1加2的和是3.0。如果您还有其他问题或需要我进行其他计算，请随时告诉我。\n"
     ]
    }
   ],
   "source": [
    "chat_messages = [\n",
    "    {\n",
    "        \"role\": \"user\",\n",
    "        \"content\": \"1加2等于多少\",\n",
    "    },\n",
    "    {\n",
    "        \"role\": \"assistant\",\n",
    "        \"content\": None,\n",
    "        \"function_call\": function_call,\n",
    "    },\n",
    "    {\n",
    "        \"role\": \"function\",\n",
    "        \"name\": function_call[\"name\"],\n",
    "        \"content\": function_return,\n",
    "    },\n",
    "]\n",
    "\n",
    "response = erniebot.ChatCompletion.create(\n",
    "    model=\"ernie-4.0\",\n",
    "    messages=chat_messages,\n",
    "    functions=functions,\n",
    ")\n",
    "print(response.get_result())"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Final\n",
    "最终整体展示以及调用（整合了Step2-Step4），可以通过以下函数进行整体的串型调用执行，获取最终想要的结果。"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 17,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "[Thought]: 用户想要知道512和798的和。我可以使用add_function工具来计算这两个数的和。\n",
      "[函数调用]结果计算中...\n",
      "[回传]最终结果生成中...\n",
      "[最终回答] 512加798的结果是1310。如果你还有其他问题，随时告诉我。\n"
     ]
    },
    {
     "data": {
      "text/plain": [
       "'512加798的结果是1310。如果你还有其他问题，随时告诉我。'"
      ]
     },
     "execution_count": 17,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "import time\n",
    "\n",
    "\n",
    "def function_call(\n",
    "    query: str,\n",
    "    functions: List[str],\n",
    ") -> str:\n",
    "    messages = [\n",
    "        {\n",
    "            \"role\": \"user\",\n",
    "            \"content\": query,\n",
    "        }\n",
    "    ]\n",
    "    # Step 2: 模型选择使用哪个函数/API\n",
    "    response = erniebot.ChatCompletion.create(\n",
    "        model=\"ernie-4.0\",\n",
    "        messages=messages,\n",
    "        functions=functions,\n",
    "    )\n",
    "\n",
    "    assert hasattr(response, \"function_call\")\n",
    "    function_call = response.function_call\n",
    "\n",
    "    assert hasattr(response.get_result(), \"thoughts\")\n",
    "    print(\"[Thought]:\", response.get_result()[\"thoughts\"])\n",
    "\n",
    "    messages.append(\n",
    "        {\n",
    "            \"role\": \"assistant\",\n",
    "            \"content\": None,\n",
    "            \"function_call\": function_call,\n",
    "        }\n",
    "    )\n",
    "    # Step 3: 执行函数调用\n",
    "    print(\"[函数调用]结果计算中...\")\n",
    "    function_return = eval(response.get_result()[\"name\"])(**eval(response.get_result()[\"arguments\"]))\n",
    "\n",
    "    messages.append(\n",
    "        {\n",
    "            \"role\": \"function\",\n",
    "            \"name\": function_call[\"name\"],\n",
    "            \"content\": function_return,\n",
    "        }\n",
    "    )\n",
    "\n",
    "    time.sleep(1)  # 防止频繁访问报错\n",
    "\n",
    "    # Step 4: 回传模型进行修饰[Option]\n",
    "    print(\"[回传]最终结果生成中...\")\n",
    "    response = erniebot.ChatCompletion.create(\n",
    "        model=\"ernie-4.0\",\n",
    "        messages=messages,\n",
    "        functions=functions,\n",
    "    )\n",
    "    print(\"[最终回答]\", response.result)\n",
    "    return response.result\n",
    "\n",
    "\n",
    "function_call(\"512加798等于多少\", functions=functions)"
   ]
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "Python 3.10.13 ('py310')",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.10.13"
  },
  "vscode": {
   "interpreter": {
    "hash": "9345dcc06c282d741efc85f9a9d5e3db79cc12ed5ca52c1d1ae239e559abfbe9"
   }
  }
 },
 "nbformat": 4,
 "nbformat_minor": 2
}
